﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using SSG.Tools;

namespace SSG.Model
{
    /// <summary>
    /// Representing a runway.
    /// </summary>
    public class Runway : Fix
    {
        public new static string KmlPlacemarkStyle = typeof (Runway).GetClassname();

        /// <summary>
        /// All available runways
        /// </summary>
        public static readonly List<Runway> Runways = new List<Runway>();

        /// <summary>
        /// Airport this runway belonging to
        /// </summary>
        public Airport Airport { get; set; }

        /// <summary>
        /// Elevation
        /// </summary>
        public Altitude Elevation { get { return base.AltitudeRestriction; } }

        /// <summary>
        /// Elevation in feet
        /// </summary>
        public double ElevationFt { get { return this.Elevation.ValueFt; } set { this.Elevation.ValueFt = value; } }

        /// <summary>
        /// Elevation in feet
        /// </summary>
        public string ElevationUnitFeet { get { return this.Elevation.ValueFtUnit; } }

        /// <summary>
        /// Airport's ICAO code
        /// </summary>
        public string AirportIcao
        {
            get
            {
                if (this.Airport != null) return this.Airport.Name;
                return !String.IsNullOrEmpty(this.CanonicalName) ? this.CanonicalNameParts[0] : null;
            }
        }

        /// <summary>
        /// Direction part of runway
        /// </summary>
        public int RunwayTrack { get; set; }

        /// <summary>
        /// Direction part of runway (exact in degrees)
        /// </summary>
        public int RunwayTrackDegrees { get; set; }

        /// <summary>
        /// Ils part of runway (exact in degrees)
        /// </summary>
        public int IlsTrackDegrees { get; set; }

        /// <summary>
        /// Ils frequency
        /// </summary>
        public double IlsFrequencyMhz { get; set; }

        /// <summary>
        /// Runway length in feet
        /// </summary>
        public int RunwayLengthFt { get; set; }

        /// <summary>
        /// Runway length in feet
        /// </summary>
        public int RunwayLengthM { get { return (int) Math.Round(this.RunwayLengthFt.FeetToMeters()); } }

        /// <summary>
        /// Runway length in meters
        /// </summary>
        public string RunwayLengthUnitMeters { get { return this.RunwayLengthM.UnitM(); } }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        /// <param name="canonicalName"></param>
        /// <param name="description"></param>
        /// <param name="latitude"></param>
        /// <param name="longitude"></param>
        public Runway(string name, string canonicalName, string description, double latitude, double longitude) : base(name, canonicalName, description, latitude, longitude)
        {
            if (String.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            base.AltitudeRestriction = new Altitude(0, Altitude.AltitudeMode.Msl, Altitude.AltitudeModifier.Exact);
        }

        /// <summary>
        /// Init by other runway
        /// </summary>
        /// <param name="runway"></param>
        public Runway(Runway runway) : this(runway.Name, runway.CanonicalName, runway.Description, runway.Latitude, runway.Longitude)
        {
            if (runway == null) throw new ArgumentNullException("runway");
            this.RunwayTrack = runway.RunwayTrack;
            this.RunwayLengthFt = runway.RunwayLengthFt;
            this.IlsFrequencyMhz = runway.IlsFrequencyMhz;
            this.IlsTrackDegrees = runway.IlsTrackDegrees;
            if (runway.AltitudeCalculated != null) this.AltitudeCalculated = new Altitude(runway.AltitudeCalculated);
            if (runway.AltitudeRestriction != null) this.AltitudeRestriction = new Altitude(runway.AltitudeRestriction);
        }

        /// <summary>
        /// Clone me
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Runway(this);
        }

        /// <summary>
        /// Is the runway the opposite runway to a given one?
        /// 10L => 28R, 12 => 30
        /// </summary>
        /// <param name="runway"></param>
        /// <returns></returns>
        public bool IsOppositeRunway(Runway runway)
        {
            if (runway == null || runway == this) return false;
            int oppDirection = runway.RunwayTrack + 18;
            if (oppDirection > 36) oppDirection -= 36;
            if (oppDirection != this.RunwayTrack) return false;

            // direction matches
            char rl = this.Name[this.Name.Length - 1];
            char orl = runway.Name[runway.Name.Length - 1];
            if (Char.IsNumber(rl) && Char.IsNumber(orl)) return true;
            if ('L' == rl && 'R' == orl) return true;
            if ('R' == rl && 'L' == orl) return true;
            return 'C' == rl && 'C' == orl;
        }

        /// <summary>
        /// Object based on Navigraph PMDG format.
        /// </summary>
        /// <param name="rwLine"></param>
        /// <returns></returns>
        public static Runway RunwayFromNavigraphPmdgFormat(string rwLine)
        {
            if (string.IsNullOrWhiteSpace(rwLine)) return null;
            rwLine = rwLine.Trim();

            // NELSON                  CZNL22 03100220 49.495836-117.295433000.0022001755
            // NURNBERG                EDDN10 08859097 49.500550  11.059169111.3009701022
            // MUNCHEN                 EDDM08R13123081 48.340670  11.751017109.3008201486
            //           1         2         3         4         5         6         7
            // 01234567890123456789012345678901234567890123456789012345678901234567890123

            string description = rwLine.Substring(0, 24).Trim();
            string icao = rwLine.Substring(24, 4).Trim();
            string rw = rwLine.Substring(28, 3).Trim();
            string rwt = rw.ReplaceNonNumericCharacters();
            double lat, lng, ilsFreq;
            int ilsTrack, rwTrack, track, elevation, rwLength;
            if (!int.TryParse(rwLine.Substring(36, 3).Trim(), NumberStyles.Integer | NumberStyles.AllowThousands, Coordinates.ParseCulture, out rwTrack)) return null;
            if (!int.TryParse(rwLine.Substring(66, 3).Trim(), NumberStyles.Integer | NumberStyles.AllowThousands, Coordinates.ParseCulture, out ilsTrack)) return null;
            if (!int.TryParse(rwLine.Substring(69, 5).Trim(), NumberStyles.Integer | NumberStyles.AllowThousands, Coordinates.ParseCulture, out elevation)) return null;
            if (!int.TryParse(rwLine.Substring(31, 5).Trim(), NumberStyles.Integer | NumberStyles.AllowThousands, Coordinates.ParseCulture, out rwLength)) return null;
            if (!int.TryParse(rwt, NumberStyles.Integer | NumberStyles.AllowThousands, Coordinates.ParseCulture, out track)) return null;
            if (!Double.TryParse(rwLine.Substring(39, 10).Trim(), NumberStyles.Float | NumberStyles.AllowThousands, Coordinates.ParseCulture, out lat)) return null;
            if (!Double.TryParse(rwLine.Substring(50, 10).Trim(), NumberStyles.Float | NumberStyles.AllowThousands, Coordinates.ParseCulture, out lng)) return null;
            if (!Double.TryParse(rwLine.Substring(60, 6).Trim(), NumberStyles.Float | NumberStyles.AllowThousands, Coordinates.ParseCulture, out ilsFreq)) return null;
            Runway runway = new Runway(rw, icao + BaseEntity.CanonicalNameSeperator + rw, description + " " + rw, lat, lng)
                {
                    IlsTrackDegrees = ilsTrack, IlsFrequencyMhz = ilsFreq,
                    ElevationFt = elevation, RunwayLengthFt = rwLength,
                    RunwayTrackDegrees = rwTrack, RunwayTrack = track
                };
            return runway;
        }

        /// <summary>
        /// Find runway
        /// </summary>
        /// <param name="icao"></param>
        /// <param name="runway"></param>
        /// <returns></returns>
        public static Runway FindForAirportByName(string icao, string runway)
        {
            string cn = (icao + BaseEntity.CanonicalNameSeperator + runway).ToUpperInvariant().Trim();
            return Runways.FirstOrDefault(rw => rw.CanonicalName.Equals(cn, StringComparison.InvariantCultureIgnoreCase));
        }

        /// <summary>
        /// Read runways
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static List<Runway> ReadNavigraphPmdgRunways(string file)
        {
            if (String.IsNullOrEmpty(file)) return null;
            if (!File.Exists(file)) return null;

            // create reader & read file
            string content = FileTools.ReadTextFile(file);
            if (String.IsNullOrEmpty(content)) return null;
            string[] lines = content.Split('\n');
            List<Runway> rws = new List<Runway>();

            for (int line = 0; lines.Length > line; line++) {
                string cl = lines[line];
                if (String.IsNullOrWhiteSpace(cl)) continue;
                if (cl.StartsWith(";")) continue;
                Runway rw = Runway.RunwayFromNavigraphPmdgFormat(cl);
                if (rw == null) throw new InvalidOperationException("Line " + (line + 1) + " cannot be parsed: " + cl);
                rws.Add(rw);
            }

            Runway.Runways.Clear();
            Runway.Runways.AddRange(rws);
            return Runway.Runways;
        }

        /// <summary>
        /// Get names from PMDG line, such as "RNW 06L RNW 06R RNW 07L RNW 07R RNW 24L RNW 24R RNW 25L RNW 25R"
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public static List<string> RunwayNamesFromPmdgLine(string line)
        {
            if (String.IsNullOrEmpty(line)) return new List<string>();
            string[] parts = line.Trim().Split(' ');
            List<string> rwyNames = new List<string>();
            for (int p = 0; p < parts.Length; p++) {
                string part = parts[p];
                if (!"RNW".Equals(part, StringComparison.InvariantCultureIgnoreCase)) continue;
                if (parts.Length > (p + 1)) {
                    rwyNames.Add(parts[++p]);
                }
            }
            return rwyNames;
        }

        /// <summary>
        /// KML
        /// </summary>
        /// <param name="level"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public new virtual string ToKml(int level, GeneratorSettings settings)
        {
            return Kml.Placemark(this.Name, this.Synopsis(), Runway.KmlPlacemarkStyle, this.Latitude, this.Longitude, level, false, settings);
        }

        /// <summary>
        /// Summary about runway
        /// </summary>
        /// <returns></returns>
        public new string Synopsis()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("elv: ");
            sb.Append(this.ElevationUnitFeet);
            sb.Append(" length: ");
            sb.Append(this.RunwayLengthUnitMeters);
            return sb.ToString();
        }
    }
}